#include "vcl_stl.h"

#include "bkbase.h"
#include "bstcp.h"
#include "bkipc.h"

#include "h_base.h"
#include "h_xml.h"
#include "h_conv.h"
using namespace h_base;

#include "simbat.h"
using namespace simbat;

#include "simbat_plugin.h"

#include <dlfcn.h>
#include <sys/stat.h>

static const int COMM_TIME_OUT = 30000;

static int SockDb;
static char *SimbatBase;
static HString SyslogDir;
static HSyslog Syslog;
static HString ShrDir;
static char ErrMsg[8192 + 1];
static SimbatPluginDbInitArg DbFunc;
static bool HasDbFunc = false;
static HDbHeader Header;
static int RecCount = 0;
static map<HString, HString> VarMap;
static bool ClusterFlag = false;                    // 是否集群
static int MyId = -1;                               // 本服务器ID（从0开始）
static HNodeRole MyRole = nrMaster;                 // 本服务器角色（MASTER/SLAVE/BACKUP）
static HString ConfigFile;
static HString ConfigLockFile;
static HString SystemName;

static bool LoadClusterInfo(HXmlTree &Cfg)
{
   vector<HString> StrVec;
   char ErrMsg[8192 + 1];

   Cfg.GetValues("CLUSTER", StrVec);
   ClusterFlag = StrVec.size() > 1;
   return true;
}

static bool LoadFromCfg(HXmlTree &Cfg, bool InitFlag = false)
{
   SyslogDir = EnvEvaluate(Cfg.GetValue("SYSLOG_DIR", "$SIMBAT_BASE/etc"));
   Syslog.Setup(SyslogDir, Cfg.GetValueUpper("SYSLOG_LEVEL", "INFO"));

   if ( !GetMyId(SimbatBase, MyId, MyRole, ErrMsg) )
   {
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   if ( !LoadClusterInfo(Cfg) )
   {
      return false;
   }
  
   ShrDir = EnvEvaluate(Cfg.GetValue("SHR_DIR", "$SIMBAT_BASE/shr"));
   setenv("SIMBAT_SHR_DIR", ShrDir.c_str(), 1);

   if ( !InitFlag )
   {
      return true;
   }

   HasDbFunc = false;

   if ( MyRole == nrMaster || MyRole == nrGateway )
   {
      memset(&DbFunc, 0, sizeof(DbFunc));
      HString PluginFileName = EnvEvaluate(Cfg.GetValue("PLUGIN_DB_STATUS"));
      if ( PluginFileName != "" )
      {
         void *PluginHandle = dlopen(PluginFileName.c_str(), RTLD_NOW);
         if ( PluginHandle == NULL )
         {
            snprintf(ErrMsg, 8192, "dlopen(%s) fail, error = %s", PluginFileName.c_str(), dlerror());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
         else
         {
            SIMBAT_PLUGIN_DB_INIT Func = (SIMBAT_PLUGIN_DB_INIT)dlsym(PluginHandle, "SimbatPluginDbInit");
            if ( Func == NULL )
            {
               snprintf(ErrMsg, 8192, "dlsym(%s) fail, error = %s", "SimbatPluginDbInit", dlerror());
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
            else
            {
               Func(&DbFunc);
               HasDbFunc = true;
               snprintf(ErrMsg, 8192, "Load plugin(%s) completed", PluginFileName.c_str());
               Syslog.Log(__FILE__, __LINE__, llInfo, "OTHER", ErrMsg);
            }
         }
      }
   }

   SystemName = Cfg.GetValue("SYSTEM_NAME", "simbat");
   return true;
}

bool Init()
{
   SimbatBase = getenv("SIMBAT_BASE");

   if ( SimbatBase == NULL )
   {
      BkPrintError("Line %d : $SIMBAT_BASE not defined", __LINE__);
      return false;
   }

   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/etc/config.xml", SimbatBase);
   ConfigFile = FileName;
   snprintf(FileName, 500, "%s/tmp/config.lck", SimbatBase);
   ConfigLockFile = FileName;

   HXmlTree Cfg;

   {
      HFileLockRead Lock(ConfigLockFile.c_str());
      if ( !Cfg.LoadFromFile(ConfigFile.c_str()) )
      {
         BkPrintError("Line %d : Load config.xml fail, %s",
            __LINE__, Cfg.GetErrorString().c_str());
         return false;
      }
   }

   if ( !LoadFromCfg(Cfg, true) )
   {
      return false;
   }

   return true;
}

static void ReturnSuccess(int Fd, const char *Result = NULL)
{
   int r;
   char ErrMsg[2048 + 1];

   r = BsWriteToSocket(Fd, "0", 1, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
   }

   if ( Result == NULL )
   {
      Result = "";
   }

   WriteToSocketHSL(Fd, Result, strlen(Result), COMM_TIME_OUT);
   close(Fd);
}

static void ReturnFail(int Fd, const char *ErrStr = NULL)
{
   int r;
   char ErrMsg[2048 + 1];

   r = BsWriteToSocket(Fd, "1", 1, COMM_TIME_OUT);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, errno == EPIPE || errno == ECONNRESET ? llDebug : llError, "ONERR", ErrMsg);
   }

   if ( ErrStr == NULL )
   {
      ErrStr = "";
   }

   WriteToSocketHSL(Fd, ErrStr, strlen(ErrStr), COMM_TIME_OUT);
   close(Fd);
}

static int Work(int ServerSock)
{
   char ErrMsg[2048 + 1];
   char ErrStr[2048 + 1];

   if ( HasDbFunc && DbFunc.Connect("", ErrStr) != 0 )
   {
      snprintf(ErrMsg, 8192, "Connect fail, errstr = %s", ErrStr);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return 8;
   }

   for ( ; ; )
   {
      int SockDb = BsAcceptConnection(ServerSock, NULL, BS_TIME_INFINITE);
      if ( SockDb < 0 )
      {
         if ( errno == EINTR )
         {
            break;
         }
         snprintf(ErrMsg, 8192, "BsAcceptConnection fail, r = %d, errno = %d", SockDb, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return 8;
      }
      char TmpStr[8192 + 1];
      int r = ReadFromSocketHSL(SockDb, TmpStr, 8192, COMM_TIME_OUT);
      if ( r < 0 )
      {
         int Errno = errno;
         snprintf(ErrMsg, 2048, "ReadFromSocketHSL fail, r = %d, errno = %d", r, Errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(SockDb);
         return 8;
      }
      TmpStr[r] = '\0';
      vector<HString> ArgVec;
      SplitString(TmpStr, '|', ArgVec, true);
      if ( ArgVec.empty() )
      {
         snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ReturnFail(SockDb, ErrMsg);
         continue;
      }
      if ( !HasDbFunc )
      {
         snprintf(ErrMsg, 2048, "No db func");
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ReturnFail(SockDb, ErrMsg);
         continue;
      }
      HString Func = ArgVec[0];
      if ( Func == "connect" )
      {
         ReturnSuccess(SockDb);
      }
      else if ( Func == "count" )
      {
         if ( ArgVec.size() < 2 )
         {
            snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         int Count = 0;
         if ( DbFunc.CountStatus(ArgVec[1].c_str(), &Count, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 8192, "CountStatus fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         snprintf(TmpStr, 8192, "%d", Count);
         ReturnSuccess(SockDb, TmpStr);
      }
      else if ( Func == "clear" )
      {
         if ( ArgVec.size() < 2 )
         {
            snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         if ( DbFunc.ClearStatus(ArgVec[1].c_str(), ErrStr) != 0 )
         {
            snprintf(ErrMsg, 8192, "ClearStatus fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         ReturnSuccess(SockDb);
      }
      else if ( Func == "write" )
      {
         if ( ArgVec.size() < 5 )
         {
            snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         if ( DbFunc.WriteStatus(ArgVec[1].c_str(), StrToInt(ArgVec[2].c_str()),
                 ArgVec[3].c_str(), ArgVec[4].c_str(), ErrStr) != 0 )
         {
            snprintf(ErrMsg, 8192, "WriteStatus fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         ReturnSuccess(SockDb);
      }
      else if ( Func == "read" )
      {
         if ( ArgVec.size() < 3 )
         {
            snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         char RecType[200 + 1];
         char Record[2048 + 1];
         if ( DbFunc.ReadStatus(ArgVec[1].c_str(), StrToInt(ArgVec[2].c_str()),
                 RecType, Record, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 8192, "ReadStatus fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         BkRightTrim(RecType);
         BkRightTrim(Record);
         snprintf(TmpStr, 8192, "%s|%s", RecType, Record);
         ReturnSuccess(SockDb, TmpStr);
      }
      else if ( Func == "rewrite" )
      {
         if ( ArgVec.size() < 5 )
         {
            snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         if ( DbFunc.RewriteStatus(ArgVec[1].c_str(), StrToInt(ArgVec[2].c_str()),
                 ArgVec[3].c_str(), ArgVec[4].c_str(), ErrStr) != 0 )
         {
            snprintf(ErrMsg, 8192, "RewriteStatus fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         ReturnSuccess(SockDb);
      }
      else if ( Func == "write_his" )
      {
         if ( ArgVec.size() < 5 )
         {
            snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         const char *s = TmpStr;
         for ( int i = 0; i < 4; i++ )
         {
            s = strchr(s, '|') + 1;
         }
         if ( DbFunc.WriteHis(ArgVec[1].c_str(), ArgVec[2].c_str(), ArgVec[3].c_str(), s, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 8192, "WriteHis fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         ReturnSuccess(SockDb);
      }
      else if ( Func == "read_statistic" )
      {
         if ( ArgVec.size() < 4 )
         {
            snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         int n;
         int Elapse[10];
         if ( DbFunc.ReadStatistic(ArgVec[1].c_str(), ArgVec[2].c_str(), ArgVec[3].c_str(), &n, Elapse, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 8192, "ReadStatistic fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         snprintf(TmpStr, 8192, "%d|%d|%d|%d|%d|%d|%d|%d|%d|%d|%d", n,
            Elapse[0], Elapse[1], Elapse[2], Elapse[3], Elapse[4],
            Elapse[5], Elapse[6], Elapse[7], Elapse[8], Elapse[9]);
         ReturnSuccess(SockDb, TmpStr);
      }
      else if ( Func == "write_statistic" )
      {
         if ( ArgVec.size() < 15 )
         {
            snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         int n;
         int Elapse[10];
         for ( int i = 0; i < 10; i++ )
         {
            Elapse[i] = StrToInt(ArgVec[i + 5].c_str());
         }
         if ( DbFunc.WriteStatistic(ArgVec[1].c_str(), ArgVec[2].c_str(), ArgVec[3].c_str(),
                 StrToInt(ArgVec[4].c_str()), Elapse, ErrStr) != 0 )
         {
            snprintf(ErrMsg, 8192, "WriteStatistic fail, errstr = %s", ErrStr);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ReturnFail(SockDb, ErrMsg);
            continue;
         }
         ReturnSuccess(SockDb);
      }
      else
      {
         snprintf(ErrMsg, 2048, "Invalid input '%s'", TmpStr);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         ReturnFail(SockDb, ErrMsg);
      }
      continue;
   }

   return 0;
}

int main(int argc, char *argv[])
{
   if ( argc != 2 )
   {
      BkPrintError("Line %d : argc != 2", __LINE__);
      return 8;
   }

   if ( !Init() )
   {
      return 8;
   }

   return Work(StrToInt(argv[1]));
}
